import { getLogger, Service } from '@gnlab/utils'
import { mcombineLatest, mmap, type MemStream } from '@gnlab/utils/rx'
import type { OrderedMap } from 'immutable'
import { OrderedSet } from 'immutable'
import { type Services } from '@/services'
import type { ChatRecord } from '@/services/contact-chat'
import type {
  TransferedExternal,
  RawLoadInfoConditions,
  LoadInfoConditions,
  ExternalContact,
  NoticeContact,
  GroupContact,
  InternalContact,
} from '@/services/contact-types'
import { Current } from './current'
import type { Contact, ContactWithTransfer } from './definition'
import { ContactType, splitContactId } from './definition'

import { Tops } from './tops'

export * from './definition'
export * from './tops'

export const logger = getLogger('services/contacts')

export enum ContactEventType {
  ExternalUpdatedByClient = 1, // 客户端更新了客户资料
  ExternalDeleted = 2, // 被客户删除
  ExternalAdded = 3, // 添加好友成功
  GroupCreated = 4, // 新群建立成功
}
export type ContactEvent =
  | { type: ContactEventType.ExternalUpdatedByClient; contactId: string }
  | { type: ContactEventType.ExternalDeleted; contactId: string }
  | { type: ContactEventType.ExternalAdded; contactId: string }
  | { type: ContactEventType.GroupCreated; contactId: string }

export class Contacts extends Service<Services> {
  private get qywxs() {
    return this.services.qywxs
  }
  private get externals() {
    return this.services.externals
  }
  private get transfers() {
    return this.services.transfers
  }
  private get groups() {
    return this.services.groups
  }
  private get internals() {
    return this.services.internals
  }
  private get notices() {
    return this.services.notices
  }

  current = new Current()
  tops = new Tops()

  // 所有联系人的集合，如需在任意联系人发生变化时获得通知，可监听此对象
  get all() {
    return Service.cached('Contacts.all', () => {
      return mcombineLatest([
        this.externals.list,
        this.internals.list,
        this.groups.list,
        this.notices.list,
        this.transfers.list,
      ]).pipe(
        mmap(([externals, internals, groups, notices, transfers]) => ({
          externals,
          groups,
          internals,
          notices,
          transfers,
        })),
      )
    })
  }

  get sources() {
    return Service.cached('Contacts.sources', () => {
      return {
        [ContactType.external]: this.externals.list,
        [ContactType.group]: this.groups.list,
        [ContactType.internal]: this.internals.list,
        [ContactType.notice]: this.notices.list,
      } as { [type in ContactType]: MemStream<OrderedMap<string, Contact>> }
    })
  }

  get types() {
    return Service.cached('Contacts.types', () => {
      return {
        [ContactType.external]: this.externals,
        [ContactType.group]: this.groups,
        [ContactType.internal]: this.internals,
        [ContactType.notice]: this.notices,
      }
    })
  }

  get loading() {
    return Service.cached('Contacts.loding', () => {
      return {
        [ContactType.external]: this.externals.loading,
        [ContactType.group]: this.groups.loading,
        [ContactType.internal]: this.internals.loading,
        [ContactType.notice]: this.notices.loading,
      }
    })
  }

  // 涉及到联系人的所有企业微信 ID 列表
  // 是在 services.qywxs 里记录的企业微信基础上，补充了转交接收到的联系人所属的企业微信
  get relatedWxids() {
    return Service.cached('Contacts.relatedWxids', () => {
      return mcombineLatest([this.qywxs.usableWxids, this.transfers.senderWxids]).pipe(
        mmap(([usableWxids, senderWxids]) => OrderedSet([...usableWxids, ...senderWxids])),
      )
    })
  }

  /**
   * ============================================
   * 数据查询快捷方式
   * ============================================
   */

  /**
   * 根据 contactId 获取联系人
   * 若未找到，返回 null
   */
  getById(contactId: string): ContactWithTransfer | null {
    return this.getOwnedById(contactId) ?? this.getTransferedById(contactId)
  }

  /**
   * 根据 contactId 获取联系人
   * 仅包括当前账号实际拥有的联系人，不包括转交获得的联系人
   */
  getOwnedById(contactId: string): Contact | null {
    return (
      this.externals.list.value.get(contactId) ??
      this.groups.list.value.get(contactId) ??
      this.internals.list.value.get(contactId) ??
      this.notices.list.value.get(contactId) ??
      null
    )
  }

  /**
   * 根据 contactId 获取转交列表里的客户
   */
  getTransferedById(contactId: string): TransferedExternal | null {
    return this.transfers.list.value.get(contactId) ?? null
  }

  /**
   * ============================================
   * 加载、更新指定联系人资料
   * ============================================
   */

  /**
   * 加载或刷新指定联系人的信息
   * 成功返回 true；失败返回 false
   *
   * tryInternal: 没能以客户身份拉取到信息时，是否要尝试以新员工身份获取信息
   *
   * 注意：出于性能考虑，应避免频繁触发此方法（例如可以先确认下联系人列表里是否已经有此联系人）
   */
  protected infoLoading: { [contactId: string]: Promise<boolean> } = {}
  async loadInfo(rawItem: RawLoadInfoConditions, tryInternal = true, byChatRecord?: ChatRecord) {
    const [ownerWxid, contactUserId] = splitContactId(rawItem.id)
    const item = { ...rawItem, ownerWxid, contactUserId, byChatRecord }

    if (!this.infoLoading[item.id]) {
      const loading = this.executeLoadInfo(item, tryInternal)
      this.infoLoading[item.id] = loading
      void loading.then(() => {
        delete this.infoLoading[item.id]
      })
    }
    return this.infoLoading[item.id]!
  }
  protected async executeLoadInfo(info: LoadInfoConditions, tryInternal = true): Promise<boolean> {
    // notice 和 group 可以同步获知是否是此类联系人，因此最先判断
    const noticeLoaded = this.notices.tryLoadInfo(info)
    if (noticeLoaded !== null) return noticeLoaded

    const groupLoaded = await this.groups.tryLoadInfo(info)
    if (groupLoaded !== null) return groupLoaded

    // 非群联系人，大概率是客户，尝试获取客户信息
    const externalLoaded = await this.externals.tryLoadInfo(info)
    if (externalLoaded !== null) return externalLoaded

    // 未获取到客户信息，可能此联系人是新员工，尝试获取员工信息
    if (tryInternal) {
      const internalLoaded = await this.internals.tryLoadInfo(info)
      return internalLoaded
    }

    return false
  }

  /**
   * 新增或更新一个联系人的信息（此方法灵活度较高，仅限内部调用，外部需调用二次封装好的方法）
   *
   * - 若 goTop=true，会同时将联系人提升到列表最前面（如果是新增，固定会将联系人加入到列表最前面）。
   * - 若联系人类型是“客户”且涉及“转交”，会同时更新其在转交列表和联系人列表里的资料。
   */
  private addOrUpdate(inputContacts: Contact | Contact[], goTop = true) {
    const contacts = Array.isArray(inputContacts) ? inputContacts : [inputContacts]
    const externals: ExternalContact[] = []
    const groups: GroupContact[] = []
    const internals: InternalContact[] = []
    const notices: NoticeContact[] = []
    for (const contact of contacts) {
      if (contact.type === ContactType.external) externals.push(contact)
      else if (contact.type === ContactType.group) groups.push(contact)
      else if (contact.type === ContactType.internal) internals.push(contact)
      else notices.push(contact)
    }
    if (externals.length) this.externals.listAddOrUpdate(externals, goTop)
    if (groups.length) this.groups.listAddOrUpdate(groups, goTop)
    if (internals.length) this.internals.listAddOrUpdate(internals, goTop)
    if (notices.length) this.notices.listAddOrUpdate(notices, goTop)
  }

  /**
   * 向联系人列表里补充一个联系人（例如接收到不在列表中的联系人的消息，或搜索到新联系人）
   */
  add(contact: Contact) {
    if (this.getById(contact.id))
      return void logger.warn(`联系人 ${contact.id} 已存在于联系人列表中，不能再次加入列表`)
    this.addOrUpdate(contact)
  }

  /**
   * 标记联系人的“最近一次聊天内容”，并将其提升到联系人列表的最前面
   *
   * 会更新两个属性：
   * 1. latestChat   最近一次聊天的具体内容
   * 2. contacted    最近一次联系的时间（对影响排序）
   *
   * 若 latestChat 为 null，代表销毁最近聊天的具体内容（说明此内容无效了）
   * 此时同样会更新 contacted
   *
   * 出于性能考虑，利用 addOrUpdate 的特性直接移动联系人位置，而不是执行一次全面排序。
   */
  recordChat(contactId: string, latestChat: ChatRecord | null) {
    this.recordChats({ [contactId]: latestChat })
  }

  // items: { contactId: latestChat, ... }
  recordChats(items: Record<string, ChatRecord | null>) {
    const updates: Contact[] = []
    for (const [contactId, latestChat] of Object.entries(items)) {
      const contact = this.getById(contactId)
      if (!contact) {
        void logger.warn(`联系人 ${contactId} 不存在于联系人列表中，无法更新其 contacted 数据`)
        continue
      }
      updates.push({
        ...contact,
        contacted: Date.now(),
        latestChat: latestChat ?? undefined,
      })
    }
    if (updates.length) this.addOrUpdate(updates)
  }

  // 手动把一个联系人移动到列表最前面
  goTop(contactId: string) {
    const contact = this.getById(contactId)
    if (!contact) return void logger.warn('找不到联系人 ' + contactId + '，无法将其移动到最前面')

    if (contact.type === ContactType.external) this.externals.goTop(contactId)
    else if (contact.type === ContactType.group) this.groups.goTop(contactId)
    else if (contact.type === ContactType.internal) this.internals.goTop(contactId)
    else this.notices.goTop(contactId)
  }
}
